#ifndef TC_ASSERT_HPP
#define TC_ASSERT_HPP

/** \file Assert.hpp
 * Declarations for various templated classes relating to assertions.
 *
 * (C) Copyright  Semih Cemiloglu
 * All rights reserved, see COPYRIGHT file for details.
 *
 * $Id: Assert.hpp 117 2006-06-19 06:49:41Z semihc $
 *
 *
 */



#include "Global.hh"
#include "Throw.hh"

namespace TC {

//! To turn on or off assertions completely
const bool AssertionsEnabled = 
#if TC_NODEBUG
  false;
#else
  true;
#endif

//! Recognized levels of assertions
enum AssertionLevels_e {
  e_ALOff       = 0,
  e_ALBasic     = 1,
  e_ALPrePost   = 2,
  e_ALInvariant = 3,
  e_ALMax       = 99
};

//! Current level of assertions.
class AssertionLevel 
{
 public:
  //! Get current level of assertions.
  static int Get() { return s_Val; }
  //! Set current level of assertions and get
  //! previous level.
  static int Set(int lvl);
 private:
  AssertionLevel(); //!< Not allowed
  static int s_Val; //!< Current value
};


// Disable a VC++ warning for use of this in initializer list in
// AssertContext class below:
#if TC_COMP_VCpp
# pragma warning(disable:4355)
#endif


/**
 * A structure to capture a context of an assertion
 * to be used by TC_ASSERT_SMART. For the detailed discussion
 * of the macro trickery see AA's Enhancing Assertions 
 * article in CUJ (August 2003).
 */
struct AssertContext
{
  // CREATORS
  AssertContext(const char* file, const int line, const char* func,
                const char* desc) :
      m_file(file), m_line(line), m_func(func),
      m_level(AssertionLevel::Get()), m_desc(desc),
      TC_ASSERT_CONTEXT_A(*this), TC_ASSERT_CONTEXT_B(*this) {}
  
  AssertContext(const AssertContext& o) :
      m_file(o.m_file), m_line(o.m_line), m_func(o.m_func),
      m_level(o.m_level), m_desc(o.m_desc),
      TC_ASSERT_CONTEXT_A(*this), TC_ASSERT_CONTEXT_B(*this) {}


  // ACCESORS
  const AssertContext& print(const char* predicate, bool value) const;
  const AssertContext& logContext() const;

  // MEMBERS
  const char* m_file;
  const int   m_line;
  const char* m_func;
  int         m_level;
  const char* m_desc;
  const AssertContext& TC_ASSERT_CONTEXT_A;
  const AssertContext& TC_ASSERT_CONTEXT_B;

 private:
  // NOT ALLOWED
  AssertContext();
  AssertContext& operator=(const AssertContext&);
  
};

inline AssertContext MakeAssertContext(const char* file, const int line, const char* func, const char* desc)
{ return AssertContext(file, line, func, desc); }

# define TC_ASSERT_CONTEXT_A(X) TC_ASSERT_CONTEXT_OP(X,B)
# define TC_ASSERT_CONTEXT_B(X) TC_ASSERT_CONTEXT_OP(X,A) 
# define TC_ASSERT_CONTEXT_OP(X, NXT) TC_ASSERT_CONTEXT_A.print(#X,(X)).TC_ASSERT_CONTEXT_ ## NXT
// See definition of TC_ASSERT_SMART below


  
//! Assertion expression evaluation, returns true if assertion
//! fails.
template <typename A>
inline bool AssertEval(A assertion)
{ return ::TC::AssertionsEnabled && !assertion; }

//! Assertion expression evaluation including the assertion level.
//! Returns true if assertion fails.
template <typename A>
inline bool AssertEval(int lvl, A assertion)
{ return AssertEval(assertion) && (::TC::AssertionLevel::Get() >= lvl); }



//! Check the assertion expression. If false, throw an exception
//! given type in the template parameters.
template <class X, typename A>
inline void AssertCheck(int lvl, A assertion, const char* desc)
{
  if(::TC::AssertionsEnabled && 
     ::TC::AssertionLevel::Get() >= lvl)
    if(!assertion)
      ::TC::Throw<X>(X(desc));
}


//! Check the assertion expression and throw a standard exception.
template <typename A>
inline void AssertCheck(A assertion,
                        const char* file, int line, const char* func,
                        const char *desc)
{
  //TODO: reason code for Assert exceptions
  if(AssertEval(assertion))
    ::TC::Throw(desc, 0, file, line, func);
}


//! Check the assertion expression and throw a standard exception.
template <typename A>
inline void AssertCheck(int lvl, A assertion, 
                        const char* file, int line, const char* func,
                        const char* desc)
{
  //TODO: Reason code
  if(AssertEval(lvl,assertion))
    ::TC::Throw(desc, 0, file, line, func);
}


//! A generic, helper template class to aid compile-time
//! assertions. Its definition is 
//! specifically not provided.
template <bool A> struct CompileAssertFailure;

//! The specialization of the above template class.
//! \see CompileAssertFailure
template <> struct CompileAssertFailure<true> {};

//! A helper template class to implement compile-time
//! asserts.
template <int I> struct CompileAssertTest {};


} // end namespace


#ifdef TC_NODEBUG

# define TC_ASSERT_COMPILE(A)     ((void)0)
# define TC_ASSERT_SIMPLE(A)      ((void)0) 
# define TC_ASSERT_LEVEL(L,A)     ((void)0)
# define TC_ASSERT_CONTEXT(A)     ((void)0)
# define TC_ASSERT_SMART(A)       ((void)0)
# define TC_ASSERT(A)             ((void)0)
# define TC_VERIFY(A)             ((void)(A))

#else

//! Compile-time (static) assert. Note the use of typedef to avoid
//! code generation by some compilers.
# define TC_ASSERT_COMPILE(A) do { \
  typedef ::TC::CompileAssertTest< \
   sizeof(::TC::CompileAssertFailure< static_cast<bool>(A) >) > \
   CompileAssertTypeDef_t; } while(false)

# define TC_ASSERT_SIMPLE(A) \
  ::TC::AssertCheck(::TC::e_ALBasic,(A),__FILE__,__LINE__,TC_FUNCTION,(#A))

# define TC_ASSERT_LEVEL(L,A) \
  ::TC::AssertCheck(L,(A),__FILE__,__LINE__,TC_FUNCTION,(#A))

# define TC_ASSERT_CONTEXT(A) \
  if(AssertEval(::TC::e_ALBasic, (A))) \
    ::TC::Throw(#A,0,__FILE__,__LINE__,TC_FUNCTION)

# define TC_ASSERT_SMART(A) \
  if(AssertEval(::TC::e_ALBasic,(A)))                                   \
    struct Local {                                                      \
      Local(const AssertContext& ac) {                                  \
        ::TC::Throw(ac.m_desc, 0, ac.m_file, ac.m_line, ac.m_func);     \
      }                                                                 \
    } localAsserter =                                                   \
      MakeAssertContext(__FILE__,__LINE__,TC_FUNCTION,#A).logContext().TC_ASSERT_CONTEXT_A

//! Simple redirection
# define TC_ASSERT(A) TC_ASSERT_SIMPLE(A)

# define TC_VERIFY(A)                                   \
  { if(!(A)) ::TC::Throw((#A),0,__FILE__,__LINE__,TC_FUNCTION); }


#endif /* TC_NODEBUG */

     
#endif /* Include guard */
